package idv.takeshi.software.productline.bookstore.domain.model.valueobject;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.annotations.CollectionOfElements;

/**
 * A value object for storing multiple texts for each different locale. 
 * @author takeshi
 *
 */
@Embeddable
public class InternationalText {
 
	private static Set<String> neededLocales = new LinkedHashSet<String>();
	private static String defaultLocale;
	
	@CollectionOfElements(fetch = FetchType.EAGER)
	private Map<String, String> neededLocaleAndTextMap = new HashMap<String, String>();
	
	/**
	 * Default constructor.
	 */
	public InternationalText(){
		if(neededLocales.size() == 0)
			throw new RuntimeException("neededLocales is not set");
		if(StringUtils.isBlank(defaultLocale))
			throw new RuntimeException("defaultLocale is not set");
	}
	
	/**
	 * Initial this instance by given localeAndTextMap.
	 * @param localeAndTextMap
	 */
	public InternationalText(Map<String, String> localeAndTextMap){
		this();
		Validate.notEmpty(localeAndTextMap, "localeAndTextMap shall always not be null/empty");
		this.neededLocaleAndTextMap = localeAndTextMap;
	}
	 
	/**
	 * @return the defaultLocale
	 */
	public static String getDefaultLocale() {
		return defaultLocale;
	}

	/**
	 * @param defaultLocale the defaultLocale to set
	 */
	public static void setDefaultLocale(String defaultLocale) {
		InternationalText.defaultLocale = defaultLocale;
	}

	/**
	 * Add needed locales
	 * @param locale
	 */
	public static void addNeededLocale(String locale) {
		Validate.notEmpty(locale, "locale shall always not be null");
		neededLocales.add(locale);
	}
	
	/**
	 * Get a set of unmodifiable neededLocales.
	 * @return
	 */
	public static Set<String> getNeededLocales(){
		return Collections.unmodifiableSet(neededLocales);
	}
	 
	/**
	 * Set text for specific locale.
	 * @param locale
	 * @param text
	 * @return old-text
	 * @throws LocaleNotNeededException 
	 */
	public String setText(String locale, String text) throws LocaleNotNeededException {
		String oldText = null;
		if(neededLocales.contains(locale)){
			oldText = neededLocaleAndTextMap.put(locale, text);
		} else{
			throw new LocaleNotNeededException("setting locale:" + locale + " is not needed");
		}
		return oldText;
	}
	 
	/**
	 * Get text by given Locale.
	 * @param locale
	 * @return
	 */
	public String getText(String locale) {
		String localeKey = null;
		String text = null;
		localeKey = StringUtils.isBlank(locale)? getDefaultLocale(): locale;
		
		if(neededLocaleAndTextMap.containsKey(localeKey)){
			text = neededLocaleAndTextMap.get(localeKey);
		} else{
			//further to find a text if locale key still not found 
			Set<String> localeKeys = neededLocaleAndTextMap.keySet();
			boolean firstFound = true;
			for(String key : localeKeys){
				//loop all locale keys and to find corresponding language locale keys
				if(key.startsWith(localeKey)){
					//set the first match locale's text
					if(firstFound){
						text = neededLocaleAndTextMap.get(key);
						firstFound = false;
					}
					//set the locale's text if default locale key met
					if(key.equals(getDefaultLocale())){
						text = neededLocaleAndTextMap.get(key);
						break;
					}
				}
			}
			//if still text not found, then set the default locale text
			if(StringUtils.isBlank(text))
				text = neededLocaleAndTextMap.get(getDefaultLocale());
		}
		
		return text;
	}
	 
	/**
	 * Text whether all needed locales and corresponding text were set.
	 * @return
	 */
	public boolean isAllNeededLocalesTextFilled() {
		boolean allSet = true;
		Set<String> setLocales = neededLocaleAndTextMap.keySet();
		if(containsInNeededLocales(setLocales)){
			Collection<String> setTexts = neededLocaleAndTextMap.values();
			for(String text : setTexts){
				if(StringUtils.isBlank(text)){
					allSet = false;
					break;
				}
			}
		} else{
			allSet = false;
		}
		return allSet;
	}
	
	/**
	 * Is whether contained in neededLocales.
	 * @param setLocales
	 * @return
	 */
	private static boolean containsInNeededLocales(Collection<String> setLocales){
		Validate.notEmpty(setLocales, "setlocales shall always not be empty");
		boolean contained = true;
		for(String locale : neededLocales){
			if(!setLocales.contains(locale)){
				contained = false;
				break;
			}
		}
		return contained;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) { return false; }
		   if (obj == this) { return true; }
		   if (obj.getClass() != getClass()) {
		     return false;
		   }
		   InternationalText rhs = (InternationalText) obj;
		   return new EqualsBuilder()
			   .append(this.neededLocaleAndTextMap, rhs.neededLocaleAndTextMap)
		       .isEquals();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(43, 47).
	       append(this.neededLocaleAndTextMap).
	       toHashCode();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
	}
	 
}
 
